Skip to content

Conversation

@oguzkocer
Copy link
Contributor

@oguzkocer oguzkocer commented Dec 1, 2025

Summary

Integrates Chucker to allow users to inspect HTTP traffic for troubleshooting network issues when contacting support.

Key points:

  • Production-ready but disabled by default - users opt-in via toggle in Help/Support screen
  • Privacy-focused: logs stay on-device, sensitive headers redacted (Authorization, Cookie, Set-Cookie, X-WP-Nonce)
  • Configurable retention periods (1 hour, 1 day, 1 week, until cleared)
  • Behind NETWORK_DEBUGGING experimental feature flag
  • Covers OkHttp clients, wordpress-rs clients, and GutenbergKit WebView requests

Changes

  • Add TrackNetworkRequestsInterceptor wrapper around Chucker in FluxC
  • Add toggle UI to Help screen and new Support screen (Compose)
  • Integrate interceptor into all network paths:
    • Main OkHttpClient (via Dagger multibindings)
    • WpApiClientProvider, WPcomLoginClient, WpLoginClient
    • GutenbergKit via GutenbergKitNetworkLogger (replays JS fetch through OkHttp)
  • Store preferences at device level (UndeletablePrefKey) to support login flow debugging

Test Plan

1. Feature toggle flow (see GIF 1)

Verify the following by following along with the GIF:

  • Feature toggle hidden when NETWORK_DEBUGGING experimental flag is disabled
  • Toggle appears in both Help screen and Support screen when flag is enabled
  • Enabling tracking shows retention period picker dialog
  • Disabling tracking shows confirmation dialog
  • Cannot disable experimental flag while tracking is active (shows error dialog)

2. Network requests captured (see GIF 2)

Enable GUTENBERG_KIT and NETWORK_DEBUGGING experimental flags, then:

  1. Open My Site → More → Users
  2. Open My Site → More → Subscribers
  3. Open the block editor (create/edit a post)
  4. Go to Help/Support → View Network Requests

Verify the following requests are captured:

  • FluxC: Search for users → find GET /rest/v1.1/sites/{site_id}/users
  • wordpress-rs: Search for subscribers → find GET /wpcom/v2/sites/{site_id}/subscribers
  • GutenbergKit: Search for block-editor → find GET /wp-block-editor/v1/sites/{site_id}/settings
chucker_pr_1.webm
chucker_pr_2.webm

Part of CMM-998

Document the plan for integrating Chucker HTTP inspector into the app
for production use (disabled by default, user opt-in for troubleshooting).

Research findings:
- 7 network paths identified, 5 are OkHttp-based (interceptable)
- OkHttpClientModule is the main path covering ~90% of traffic
- 2 HttpURLConnection usages cannot be intercepted (edge cases)
- Wrapper interceptor approach recommended for runtime enable/disable
…tion

Add an OkHttp interceptor that will serve as the foundation for HTTP
traffic inspection via Chucker. The interceptor is disabled by default
and can be toggled by users in the Help screen for troubleshooting.

Changes:
- Add `TrackNetworkRequestsInterceptor` in FluxC with preference interface
- Add `TrackNetworkRequestsModule` to inject interceptor into OkHttpClient
- Add preference `IS_TRACK_NETWORK_REQUESTS_ENABLED` in `AppPrefs`
- Add toggle UI in Help screen below "Contact email"
Add Chucker v4.2.0 as the HTTP traffic inspection backend for the
Track Network Requests feature. The interceptor now delegates to
ChuckerInterceptor when the feature is enabled.

Configuration:
- 1-hour retention period
- Sensitive headers redacted (Authorization, Cookie, Set-Cookie, X-WP-Nonce)
- Notifications disabled (custom UI entry point to be added)
- Max content length: 250KB

Changes:
- Add Chucker dependency to libs.versions.toml and build.gradle files
- Update TrackNetworkRequestsInterceptor to create and use ChuckerInterceptor
- Simplify TrackNetworkRequestsModule (Chucker is now an implementation detail)
Add a button in the Help screen that launches Chucker's traffic
inspector UI. The button is only visible when Track Network Requests
is enabled.

Changes:
- Add `viewNetworkRequestsButton` to help_activity.xml
- Add `view_network_requests` string resource
- Update `setupTrackNetworkRequestsToggle()` to show/hide button and handle click
…cking

Implement retention period selection when enabling Track Network Requests,
with dialog-based enable/disable flow for better user communication.

Features:
- Retention period options: 1 Hour, 1 Day, 1 Week, Until Cleared
- Enable dialog with privacy explanation and retention picker
- Disable dialog with guidance on clearing logs manually
- "View Network Requests" now shows current retention period

Technical changes:
- Add `NetworkRequestsRetentionPeriod` enum with stable int values for persistence
- Interceptor lazily creates and recreates ChuckerInterceptor when retention changes
- Add `dialog_title_with_message.xml` layout for dialogs with description text
- Use `setSwitchCheckedSilently()` to prevent listener loops on cancel

Changes:
- Add retention period preference to `AppPrefs` and `AppPrefsWrapper`
- Update `TrackNetworkRequestsPreference` interface with `getRetentionPeriod()`
- Add enable/disable dialogs to `HelpActivity`
- Update `help_activity.xml` with container showing retention info
- Add string resources for retention options and dialog messages
Enable network request tracking for OAuth login flows by adding
interceptors to WPcomLoginClient. Move tracking preferences to
UndeletablePrefKey so they persist across logout/login cycles,
allowing users to troubleshoot login issues.

Changes:
- Add interceptors to WPcomLoginClient for OAuth token exchange
- Move IS_TRACK_NETWORK_REQUESTS_ENABLED to UndeletablePrefKey
- Move TRACK_NETWORK_REQUESTS_RETENTION_PERIOD to UndeletablePrefKey
- Add documentation for Chucker export API options
Integrates GutenbergKit's WebView network requests with Chucker by
replaying them through OkHttp. This allows editor network traffic
to appear alongside native app requests in the unified log.

Changes:
- Add `GutenbergKitNetworkLogger` to replay JS fetch requests through OkHttp
- Add `isNetworkLoggingEnabled` to `FeatureConfig` and `EditorConfiguration`
- Wire up `NetworkRequestListener` in `GutenbergKitActivity`
- Update GutenbergKit to PR #238 branch with network logging support
Remove verbose debug logging used during development.
Keep error logging for diagnosing failures.
Hide the Track Network Requests section in Help behind an experimental
feature flag. When the flag is disabled, the section is completely
hidden. When trying to disable the flag while tracking is enabled,
show an error dialog instructing the user to disable tracking first.

Changes:
- Add NETWORK_DEBUGGING to ExperimentalFeatures.Feature enum
- Add validation in ExperimentalFeaturesViewModel to prevent disabling
  the flag while tracking is active
- Add NetworkDebuggingErrorDialog composable for the error state
- Update HelpActivity to check the feature flag before showing section
- Wrap track network requests UI in a single container for cleaner
  visibility management
Add suppress annotations for:
- LongMethod in ExperimentalFeaturesActivity.onCreate (Compose UI)
- TooGenericExceptionCaught in GutenbergKitNetworkLogger (intentional)
- MagicNumber in GutenbergKitNetworkLogger (HTTP status codes)
Duplicate the network tracking functionality from HelpActivity to the
new Compose-based SupportActivity (behind MODERN_SUPPORT flag).

Also update help_activity.xml to show retention info under the toggle
instead of under the view button, for consistency between both UIs.

Changes:
- Add NetworkTrackingState and DialogEvent to SupportViewModel
- Add NetworkTrackingToggleItem composable to SupportScreen
- Add dialog handling and Chucker navigation to SupportActivity
- Restructure help_activity.xml for consistent retention info placement
Wire `TrackNetworkRequestsInterceptor` into all wordpress-rs based
network clients so their requests are captured when network debugging
is enabled.

Changes:
- `WpApiClientProvider`: Accept interceptors via Dagger `@Named` set
- `ApplicationModule`: Pass interceptor to `WpLoginClient`
- `DataViewViewModel`: Inject interceptor for `WpComApiClient`
- `JetpackConnectionHelper`: Inject interceptor for connection client
- `AddSubscribersViewModel`: Inject interceptor for subscriber API
- Child ViewModels: Pass interceptor to parent `DataViewViewModel`
GutenbergKit v0.11.0 renamed `NetworkRequest` to `RecordedNetworkRequest`
and added a `statusText` field, allowing us to remove our custom HTTP
status code mapping.

Changes:
- Update GutenbergKit version to v0.11.0
- Rename NetworkRequest to RecordedNetworkRequest
- Use statusText from RecordedNetworkRequest instead of toStatusMessage()
Replace hardcoded "interceptors" strings with the constant to prevent
typos and enable easier refactoring.
@oguzkocer oguzkocer added this to the 26.5 milestone Dec 1, 2025
@dangermattic
Copy link
Collaborator

dangermattic commented Dec 1, 2025

3 Warnings
⚠️ strings.xml files should only be updated on release branches, when the translations are downloaded by our automation.
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
⚠️ Class GutenbergKitNetworkLogger is missing tests, but unit-tests-exemption label was set to ignore this.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 1, 2025

Project dependencies changes

list
+ New Dependencies
androidx.lifecycle:lifecycle-livedata-ktx:2.10.0
androidx.palette:palette:1.0.0
androidx.palette:palette-ktx:1.0.0
com.github.chuckerteam.chucker:library:4.2.0
org.brotli:dec:0.1.2

! Upgraded Dependencies
androidx.databinding:viewbinding:8.11.1, (changed from 8.7.3)
org.wordpress.gutenbergkit:android:v0.11.1, (changed from v0.10.2)
tree
-+--- androidx.databinding:viewbinding:8.7.3
++--- androidx.databinding:viewbinding:8.7.3 -> 8.11.1
++--- androidx.navigation:navigation-compose:2.9.6
+|    \--- androidx.navigation:navigation-compose-android:2.9.6
+|         +--- androidx.activity:activity:1.8.0 -> 1.10.1
+|         |    +--- androidx.core:core-ktx:1.13.0 -> 1.16.0
+|         |    |    \--- androidx.core:core:1.16.0
+|         |    |         \--- androidx.lifecycle:lifecycle-runtime:2.6.2 -> 2.10.0
+|         |    |              \--- androidx.lifecycle:lifecycle-runtime-android:2.10.0
+|         |    |                   +--- androidx.lifecycle:lifecycle-common:2.10.0
+|         |    |                   |    \--- androidx.lifecycle:lifecycle-common-jvm:2.10.0
+|         |    |                   |         \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         |    |                   \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         |    +--- androidx.lifecycle:lifecycle-viewmodel:2.6.1 -> 2.10.0
+|         |    |    \--- androidx.lifecycle:lifecycle-viewmodel-android:2.10.0
+|         |    |         \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         |    \--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1 -> 2.10.0
+|         |         \--- androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.10.0
+|         |              +--- androidx.lifecycle:lifecycle-livedata-core:2.10.0
+|         |              |    \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         |              \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         +--- androidx.activity:activity-compose:1.8.0 -> 1.10.1
+|         |    +--- androidx.activity:activity-ktx:1.10.1
+|         |    |    +--- androidx.lifecycle:lifecycle-runtime-ktx:2.6.1 -> 2.10.0
+|         |    |    |    \--- androidx.lifecycle:lifecycle-runtime-ktx-android:2.10.0
+|         |    |    |         \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         |    |    \--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1 -> 2.10.0
+|         |    |         \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         |    +--- androidx.compose.runtime:runtime-saveable:1.7.0 -> 1.9.4
+|         |    |    \--- androidx.compose.runtime:runtime-saveable-android:1.9.4
+|         |    |         \--- androidx.lifecycle:lifecycle-runtime-compose:2.9.4 -> 2.10.0
+|         |    |              \--- androidx.lifecycle:lifecycle-runtime-compose-android:2.10.0
+|         |    |                   \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         |    \--- androidx.compose.ui:ui:1.0.1 -> 1.9.4
+|         |         \--- androidx.compose.ui:ui-android:1.9.4
+|         |              \--- androidx.compose.ui:ui-text:1.9.4
+|         |                   \--- androidx.compose.ui:ui-text-android:1.9.4
+|         |                        \--- androidx.emoji2:emoji2:1.4.0
+|         |                             \--- androidx.lifecycle:lifecycle-process:2.4.1 -> 2.10.0
+|         |                                  \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|         \--- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2 -> 2.10.0
+|              \--- androidx.lifecycle:lifecycle-viewmodel-compose-android:2.10.0
+|                   \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
-+--- project :libs:image-editor
-|    \--- androidx.databinding:viewbinding:8.7.3 (*)
++--- project :libs:analytics
+|    \--- androidx.preference:preference:1.2.1
+|         \--- androidx.appcompat:appcompat:1.1.0 -> 1.7.1
+|              \--- androidx.fragment:fragment:1.5.4 -> 1.8.9
+|                   \--- androidx.loader:loader:1.0.0
+|                        \--- androidx.lifecycle:lifecycle-livedata:2.0.0 -> 2.10.0
+|                             +--- androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0
+|                             |    \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+|                             \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
-+--- project :libs:editor
-|    \--- org.wordpress.gutenbergkit:android:v0.10.2
-|         +--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.21 -> 2.2.10 (*)
-|         +--- androidx.core:core-ktx:1.13.1 -> 1.16.0 (*)
-|         +--- androidx.appcompat:appcompat:1.7.0 -> 1.7.1 (*)
-|         +--- com.google.android.material:material:1.12.0 (*)
-|         +--- androidx.webkit:webkit:1.11.0 -> 1.14.0 (*)
-|         +--- com.google.code.gson:gson:2.8.9 -> 2.13.2
-|         |    \--- com.google.errorprone:error_prone_annotations:2.41.0
-|         +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2 (*)
-|         \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.21 -> 2.2.21 (*)
++--- project :libs:image-editor
+|    \--- androidx.databinding:viewbinding:8.7.3 -> 8.11.1 (*)
-+--- project :libs:fluxc
-|    +--- androidx.room:room-runtime:2.8.3
-|    |    \--- androidx.room:room-runtime-android:2.8.3
-|    |         +--- androidx.annotation:annotation:1.9.1 (*)
-|    |         +--- androidx.annotation:annotation-experimental:1.5.0 -> 1.5.1 (*)
-|    |         +--- androidx.arch.core:core-runtime:2.2.0 (*)
-|    |         +--- androidx.collection:collection:1.5.0 (*)
-|    |         +--- androidx.room:room-common:2.8.3
-|    |         |    \--- androidx.room:room-common-jvm:2.8.3
-|    |         |         +--- androidx.annotation:annotation:1.9.1 (*)
-|    |         |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
-|    |         |         +--- androidx.room:room-ktx:2.8.3 (c)
-|    |         |         +--- androidx.room:room-runtime:2.8.3 (c)
-|    |         |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
-|    |         +--- androidx.sqlite:sqlite:2.6.1
-|    |         |    \--- androidx.sqlite:sqlite-android:2.6.1
-|    |         |         +--- androidx.annotation:annotation:1.9.1 (*)
-|    |         |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
-|    |         |         +--- androidx.sqlite:sqlite-framework:2.6.1 (c)
-|    |         |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
-|    |         +--- androidx.sqlite:sqlite-framework:2.6.1
-|    |         |    \--- androidx.sqlite:sqlite-framework-android:2.6.1
-|    |         |         +--- androidx.annotation:annotation:1.8.1 -> 1.9.1 (*)
-|    |         |         +--- androidx.sqlite:sqlite:2.6.1 (*)
-|    |         |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
-|    |         |         +--- androidx.sqlite:sqlite:2.6.1 (c)
-|    |         |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
-|    |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
-|    |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 -> 1.10.2 (*)
-|    |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 -> 1.10.2 (*)
-|    |         +--- org.jspecify:jspecify:1.0.0
-|    |         +--- androidx.room:room-common:2.8.3 (c)
-|    |         +--- androidx.room:room-ktx:2.8.3 (c)
-|    |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
-|    \--- androidx.room:room-ktx:2.8.3
-|         +--- androidx.room:room-common:2.8.3 (*)
-|         +--- androidx.room:room-runtime:2.8.3 (*)
-|         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
-|         +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 -> 1.10.2 (*)
-|         +--- androidx.room:room-common:2.8.3 (c)
-|         +--- androidx.room:room-runtime:2.8.3 (c)
-|         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
++--- project :libs:editor
+|    +--- org.wordpress.gutenbergkit:android:v0.11.1
+|    |    +--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.21 -> 2.2.10 (*)
+|    |    +--- androidx.core:core-ktx:1.13.1 -> 1.16.0 (*)
+|    |    +--- androidx.appcompat:appcompat:1.7.0 -> 1.7.1 (*)
+|    |    +--- com.google.android.material:material:1.12.0 (*)
+|    |    +--- androidx.webkit:webkit:1.11.0 -> 1.14.0 (*)
+|    |    +--- com.google.code.gson:gson:2.8.9 -> 2.13.2
+|    |    |    \--- com.google.errorprone:error_prone_annotations:2.41.0
+|    |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2 (*)
+|    |    \--- org.jetbrains.kotlin:kotlin-stdlib:2.0.21 -> 2.2.21 (*)
+|    \--- com.automattic.tracks:crashlogging:6.0.6
+|         \--- io.sentry:sentry-android-core -> 8.18.0
+|              \--- androidx.lifecycle:lifecycle-common-java8:2.2.0 -> 2.10.0
+|                   \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
-\--- org.wordpress.gutenbergkit:android:v0.10.2 (*)
++--- project :libs:fluxc
+|    +--- com.github.chuckerteam.chucker:library:4.2.0
+|    |    +--- org.jetbrains.kotlin:kotlin-stdlib:2.2.0 -> 2.2.21 (*)
+|    |    +--- com.google.android.material:material:1.12.0 (*)
+|    |    +--- androidx.constraintlayout:constraintlayout:2.2.1 (*)
+|    |    +--- androidx.palette:palette-ktx:1.0.0
+|    |    |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.2.50 -> 2.2.21 (*)
+|    |    |    \--- androidx.palette:palette:1.0.0
+|    |    |         +--- androidx.core:core:1.0.0 -> 1.16.0 (*)
+|    |    |         \--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
+|    |    +--- androidx.activity:activity-ktx:1.10.1 (*)
+|    |    +--- androidx.fragment:fragment-ktx:1.8.8 -> 1.8.9 (*)
+|    |    +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.1 -> 2.10.0 (*)
+|    |    +--- androidx.lifecycle:lifecycle-livedata-ktx:2.9.1 -> 2.10.0
+|    |    |    +--- androidx.lifecycle:lifecycle-livedata:2.10.0 (*)
+|    |    |    +--- androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0 (*)
+|    |    |    +--- org.jetbrains.kotlin:kotlin-stdlib:2.0.21 -> 2.2.21 (*)
+|    |    |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0 -> 1.10.2 (*)
+|    |    |    +--- androidx.lifecycle:lifecycle-common:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-livedata:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-livedata-core:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-process:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-runtime:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-runtime-compose:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-runtime-ktx:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-service:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0 (c)
+|    |    |    +--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.10.0 (c)
+|    |    |    \--- androidx.lifecycle:lifecycle-common-java8:2.10.0 (c)
+|    |    +--- androidx.room:room-ktx:2.7.2 -> 2.8.3
+|    |    |    +--- androidx.room:room-common:2.8.3
+|    |    |    |    \--- androidx.room:room-common-jvm:2.8.3
+|    |    |    |         +--- androidx.annotation:annotation:1.9.1 (*)
+|    |    |    |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
+|    |    |    |         +--- androidx.room:room-ktx:2.8.3 (c)
+|    |    |    |         +--- androidx.room:room-runtime:2.8.3 (c)
+|    |    |    |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
+|    |    |    +--- androidx.room:room-runtime:2.8.3
+|    |    |    |    \--- androidx.room:room-runtime-android:2.8.3
+|    |    |    |         +--- androidx.annotation:annotation:1.9.1 (*)
+|    |    |    |         +--- androidx.annotation:annotation-experimental:1.5.0 -> 1.5.1 (*)
+|    |    |    |         +--- androidx.arch.core:core-runtime:2.2.0 (*)
+|    |    |    |         +--- androidx.collection:collection:1.5.0 (*)
+|    |    |    |         +--- androidx.room:room-common:2.8.3 (*)
+|    |    |    |         +--- androidx.sqlite:sqlite:2.6.1
+|    |    |    |         |    \--- androidx.sqlite:sqlite-android:2.6.1
+|    |    |    |         |         +--- androidx.annotation:annotation:1.9.1 (*)
+|    |    |    |         |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
+|    |    |    |         |         +--- androidx.sqlite:sqlite-framework:2.6.1 (c)
+|    |    |    |         |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
+|    |    |    |         +--- androidx.sqlite:sqlite-framework:2.6.1
+|    |    |    |         |    \--- androidx.sqlite:sqlite-framework-android:2.6.1
+|    |    |    |         |         +--- androidx.annotation:annotation:1.8.1 -> 1.9.1 (*)
+|    |    |    |         |         +--- androidx.sqlite:sqlite:2.6.1 (*)
+|    |    |    |         |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
+|    |    |    |         |         +--- androidx.sqlite:sqlite:2.6.1 (c)
+|    |    |    |         |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
+|    |    |    |         +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
+|    |    |    |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 -> 1.10.2 (*)
+|    |    |    |         +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 -> 1.10.2 (*)
+|    |    |    |         +--- org.jspecify:jspecify:1.0.0
+|    |    |    |         +--- androidx.room:room-common:2.8.3 (c)
+|    |    |    |         +--- androidx.room:room-ktx:2.8.3 (c)
+|    |    |    |         \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
+|    |    |    +--- org.jetbrains.kotlin:kotlin-stdlib -> 2.2.21 (*)
+|    |    |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 -> 1.10.2 (*)
+|    |    |    +--- androidx.room:room-common:2.8.3 (c)
+|    |    |    +--- androidx.room:room-runtime:2.8.3 (c)
+|    |    |    \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.2.21 (c)
+|    |    +--- androidx.room:room-runtime:2.7.2 -> 2.8.3 (*)
+|    |    +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2 (*)
+|    |    +--- com.google.code.gson:gson:2.13.1 -> 2.13.2 (*)
+|    |    +--- org.brotli:dec:0.1.2
+|    |    +--- com.squareup.okhttp3:okhttp:4.12.0 -> 5.3.2 (*)
+|    |    +--- com.squareup.okhttp3:okhttp -> 5.3.2 (*)
+|    |    \--- androidx.databinding:viewbinding:8.11.1 (*)
+|    +--- androidx.room:room-runtime:2.8.3 (*)
+|    \--- androidx.room:room-ktx:2.8.3 (*)
++--- org.wordpress.gutenbergkit:android:v0.11.1 (*)
++--- androidx.work:work-runtime:2.11.0
+|    \--- androidx.lifecycle:lifecycle-service:2.6.2 -> 2.10.0
+|         \--- androidx.lifecycle:lifecycle-livedata-ktx:2.10.0 (c)
+\--- com.github.chuckerteam.chucker:library:4.2.0 (*)

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 1, 2025

Project manifest changes for WordPress

The following changes in the WordPress's merged AndroidManifest.xml file were detected (build variant: wordpressVanillaRelease):

--- ./build/reports/diff_manifest/WordPress/wordpressVanillaRelease/base_manifest.txt	2025-12-02 18:42:26.752531601 +0000
+++ ./build/reports/diff_manifest/WordPress/wordpressVanillaRelease/head_manifest.txt	2025-12-02 18:43:16.172943961 +0000
@@ -67,7 +67,12 @@
         </intent>
         <!-- required for Android 11 (API level 30) or higher -->
         <package android:name="com.jetpack.android" />
-        <!-- Camera -->
+
+        <intent>
+            <action android:name="android.intent.action.CREATE_DOCUMENT" />
+
+            <data android:mimeType="*/*" />
+        </intent> <!-- Camera -->
         <intent>
             <action android:name="android.media.action.IMAGE_CAPTURE" />
         </intent> <!-- Docs -->
@@ -96,7 +101,9 @@
         </intent>
     </queries>
 
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission
+        android:name="android.permission.WAKE_LOCK"
+        android:maxSdkVersion="25" />
 
     <permission
         android:name="org.wordpress.android.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
@@ -1203,6 +1210,36 @@
             android:theme="@style/Base.ImageEditorTheme" >
         </activity>
         <activity
+            android:name="com.chuckerteam.chucker.internal.ui.MainActivity"
+            android:label="@string/chucker_name"
+            android:launchMode="singleTask"
+            android:taskAffinity="com.chuckerteam.chucker.task"
+            android:theme="@style/Chucker.Theme" />
+        <activity
+            android:name="com.chuckerteam.chucker.internal.ui.transaction.TransactionActivity"
+            android:parentActivityName="com.chuckerteam.chucker.internal.ui.MainActivity"
+            android:theme="@style/Chucker.Theme" />
+
+        <service
+            android:name="com.chuckerteam.chucker.internal.support.ClearDatabaseService"
+            android:exported="false"
+            android:permission="android.permission.BIND_JOB_SERVICE" />
+
+        <receiver
+            android:name="com.chuckerteam.chucker.internal.support.ClearDatabaseJobIntentServiceReceiver"
+            android:exported="false" />
+
+        <provider
+            android:name="com.chuckerteam.chucker.internal.support.ChuckerFileProvider"
+            android:authorities="org.wordpress.android.com.chuckerteam.chucker.provider"
+            android:exported="false"
+            android:grantUriPermissions="true" >
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/chucker_provider_paths" />
+        </provider>
+
+        <activity
             android:name="org.wordpress.android.editor.WPGutenbergWebViewActivity"
             android:theme="@style/Base.GutenbergWebView" />
         <activity

Go to https://buildkite.com/automattic/wordpress-android/builds/24060/canvas?sid=019ae05a-4a54-42a1-845a-625710d8b800, click on the Artifacts tab and audit the files.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 1, 2025

Project manifest changes for WordPress

The following changes in the WordPress's merged AndroidManifest.xml file were detected (build variant: jetpackVanillaRelease):

--- ./build/reports/diff_manifest/WordPress/jetpackVanillaRelease/base_manifest.txt	2025-12-02 18:42:10.901478206 +0000
+++ ./build/reports/diff_manifest/WordPress/jetpackVanillaRelease/head_manifest.txt	2025-12-02 18:42:59.901721758 +0000
@@ -66,7 +66,12 @@
         </intent>
         <!-- required for Android 11 (API level 30) or higher -->
         <package android:name="com.jetpack.android" />
-        <!-- Camera -->
+
+        <intent>
+            <action android:name="android.intent.action.CREATE_DOCUMENT" />
+
+            <data android:mimeType="*/*" />
+        </intent> <!-- Camera -->
         <intent>
             <action android:name="android.media.action.IMAGE_CAPTURE" />
         </intent> <!-- Docs -->
@@ -95,7 +100,9 @@
         </intent>
     </queries>
 
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission
+        android:name="android.permission.WAKE_LOCK"
+        android:maxSdkVersion="25" />
 
     <permission
         android:name="com.jetpack.android.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
@@ -1230,6 +1237,36 @@
             android:theme="@style/Base.ImageEditorTheme" >
         </activity>
         <activity
+            android:name="com.chuckerteam.chucker.internal.ui.MainActivity"
+            android:label="@string/chucker_name"
+            android:launchMode="singleTask"
+            android:taskAffinity="com.chuckerteam.chucker.task"
+            android:theme="@style/Chucker.Theme" />
+        <activity
+            android:name="com.chuckerteam.chucker.internal.ui.transaction.TransactionActivity"
+            android:parentActivityName="com.chuckerteam.chucker.internal.ui.MainActivity"
+            android:theme="@style/Chucker.Theme" />
+
+        <service
+            android:name="com.chuckerteam.chucker.internal.support.ClearDatabaseService"
+            android:exported="false"
+            android:permission="android.permission.BIND_JOB_SERVICE" />
+
+        <receiver
+            android:name="com.chuckerteam.chucker.internal.support.ClearDatabaseJobIntentServiceReceiver"
+            android:exported="false" />
+
+        <provider
+            android:name="com.chuckerteam.chucker.internal.support.ChuckerFileProvider"
+            android:authorities="com.jetpack.android.com.chuckerteam.chucker.provider"
+            android:exported="false"
+            android:grantUriPermissions="true" >
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/chucker_provider_paths" />
+        </provider>
+
+        <activity
             android:name="org.wordpress.android.editor.WPGutenbergWebViewActivity"
             android:theme="@style/Base.GutenbergWebView" />
         <activity

Go to https://buildkite.com/automattic/wordpress-android/builds/24060/canvas?sid=019ae05a-4a55-447f-8456-397da1321243, click on the Artifacts tab and audit the files.

@oguzkocer
Copy link
Contributor Author

Adding unit-tests-exemption for TrackNetworkRequestsInterceptor and GutenbergKitNetworkLogger:

These are integration/wiring classes that wrap external dependencies (Chucker, OkHttp). Unit tests would primarily mock these dependencies without catching real bugs. The meaningful testing is done manually - verifying requests actually appear in Chucker across different network paths (FluxC, wordpress-rs, GutenbergKit), as demonstrated in the test plan GIFs.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 1, 2025

App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
FlavorJalapeno
Build TypeDebug
Versionpr22381-dc1e76f
Commitdc1e76f
Direct Downloadwordpress-prototype-build-pr22381-dc1e76f.apk
Note: Google Login is not supported on these builds.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 1, 2025

App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
FlavorJalapeno
Build TypeDebug
Versionpr22381-dc1e76f
Commitdc1e76f
Direct Downloadjetpack-prototype-build-pr22381-dc1e76f.apk
Note: Google Login is not supported on these builds.

- Add @nonnull annotation to ApplicationModule parameter
- Add @SuppressLint("InflateParams") for AlertDialog custom title inflation
- Update SupportViewModelTest with appPrefsWrapper and experimentalFeatures
- Update DataViewViewModelTest with trackNetworkRequestsInterceptor
- Update ExperimentalFeaturesViewModelTest with appPrefsWrapper
- Update TermsViewModelTest with trackNetworkRequestsInterceptor
@codecov
Copy link

codecov bot commented Dec 1, 2025

Codecov Report

❌ Patch coverage is 19.93769% with 257 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.96%. Comparing base (2be71e4) to head (dc1e76f).
⚠️ Report is 1 commits behind head on trunk.

Files with missing lines Patch % Lines
...wordpress/android/support/main/ui/SupportScreen.kt 0.00% 125 Missing ⚠️
...d/fluxc/network/TrackNetworkRequestsInterceptor.kt 0.00% 41 Missing ⚠️
...droid/ui/posts/editor/GutenbergKitNetworkLogger.kt 0.00% 33 Missing ⚠️
...experimentalfeatures/ExperimentalFeaturesScreen.kt 0.00% 20 Missing ⚠️
...erimentalfeatures/ExperimentalFeaturesViewModel.kt 56.25% 5 Missing and 2 partials ⚠️
.../java/org/wordpress/android/ui/prefs/AppPrefs.java 0.00% 6 Missing ⚠️
.../org/wordpress/android/ui/prefs/AppPrefsWrapper.kt 0.00% 4 Missing ⚠️
...droid/fluxc/network/rest/wpapi/WPcomLoginClient.kt 0.00% 4 Missing ⚠️
...dpress/android/support/main/ui/SupportViewModel.kt 93.61% 3 Missing ⚠️
...i/jetpackrestconnection/JetpackConnectionHelper.kt 0.00% 3 Missing ⚠️
... and 6 more
Additional details and impacted files
@@            Coverage Diff             @@
##            trunk   #22381      +/-   ##
==========================================
- Coverage   39.02%   38.96%   -0.06%     
==========================================
  Files        2203     2205       +2     
  Lines      106351   106648     +297     
  Branches    15061    15085      +24     
==========================================
+ Hits        41501    41555      +54     
- Misses      61359    61601     +242     
- Partials     3491     3492       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@oguzkocer oguzkocer requested a review from jkmassel December 2, 2025 00:25
@oguzkocer oguzkocer marked this pull request as ready for review December 2, 2025 00:25
@oguzkocer oguzkocer requested a review from a team as a code owner December 2, 2025 00:25
var selectedIndex = periods.indexOf(currentPeriod)

@SuppressLint("InflateParams") // Parent is null because AlertDialog attaches it internally
val titleView = layoutInflater.inflate(R.layout.dialog_title_with_message, null).apply {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Feels a bit odd to inflate a layout and a dialog inside a Jetpack Compose activity. What about using a state instead of an event, and a composable AlertDialog?

Note: If we need to unblock this PR, I'm happy to approve it as it is, and work on the changes later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be addressed in 6505f42, let me know what you think!

appPrefsWrapper = appPrefsWrapper,
experimentalFeatures = experimentalFeatures
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ It would be great to add some simple tests for the new functionality in the VM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be addressed in f20cacc

Note that I followed the existing patterns in this test file by duplicating the account setup mocks. As a future improvement, we could extract these into shared helpers (e.g., setupLoggedInAccountMocks() / setupLoggedOutAccountMocks()) to improve readability across all init tests.

}

fun onEnableTrackingConfirmed(period: NetworkRequestsRetentionPeriod) {
appPrefsWrapper.trackNetworkRequestsRetentionPeriod = period.value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using viewModelScope in these functions as well, just to be sure we are not blocking the UI thread

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify which operation you're concerned might block the UI thread? The SharedPreferences writes use apply() (async), and the StateFlow updates are in-memory. Happy to wrap in viewModelScope if there's a specific concern I'm missing!

oguzkocer and others added 3 commits December 2, 2025 09:39
…tentionPeriodDisplayString into separate methods for better readability and reusability: - getRetentionPeriodStringRes: returns the string resource ID - getRetentionPeriodDisplayString: resolves it to a String Changes: - Extract resource ID mapping to dedicated method - Simplify the display string method to delegate

Co-authored-by: Adalberto Plaza <[email protected]>
Replace View-based AlertDialogs with Compose AlertDialogs in SupportScreen.
This addresses PR feedback about using Compose dialogs in a Compose activity.

Changes:
- Add reusable SingleChoiceAlertDialog component
- Convert DialogEvent (SharedFlow) to DialogState (StateFlow) in ViewModel
- Add EnableTrackingDialog and DisableTrackingDialog composables
- Remove XML layout inflation and View-based dialog code from Activity
Cover the new network tracking dialog state management with tests for:
- init() network debugging state based on feature flag and preferences
- onNetworkTrackingToggle showing enable/disable dialogs
- onEnableTrackingConfirmed/onDisableTrackingConfirmed state updates
- onDialogDismissed without side effects
- onRetentionPeriodSelected updating dialog state

Changes:
- Add 10 new tests in SupportViewModelTest
- Move init-related tests to existing init() region
- Create new region for dialog interaction tests
@oguzkocer oguzkocer requested a review from adalpari December 2, 2025 15:28
@oguzkocer
Copy link
Contributor Author

Regarding SonarQube warnings:

  1. Unused import in TrackNetworkRequestsModule.kt - False positive. The dagger.Module import on line 4 is used for the @Module annotation on line 21.

  2. 9 parameters in SingleChoiceAlertDialog - This follows standard Compose API conventions. Compose's own AlertDialog has 12+ parameters. Using a data class would add unnecessary indirection for a UI component where explicit parameters provide better discoverability and flexibility.

Copy link
Contributor

@adalpari adalpari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and works as expected! Thanks for jumping into the network tracker!
🚢

Copy link
Member

@dcalhoun dcalhoun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we await a fix in a new release from GutenbergKit before merging this.

Also, I noted what appears to be a crash in Chucker when quickly switching tabs, but this may be unrelated to our code. I share it nonetheless.

Chucker crash on tab switch
Screen_Recording_20251202_100728_Jetpack.Pre-Alpha.mp4

@oguzkocer
Copy link
Contributor Author

Also, I noted what appears to be a crash in Chucker when quickly switching tabs, but this may be unrelated to our code. I share it nonetheless.

I was able to reproduce this crash. The bug is in Chucker UI implementation and unfortunately I don't think there is anything we can do on our end.

E  FATAL EXCEPTION: main
  Process: com.jetpack.android.beta, PID: 5719
  java.lang.IllegalStateException: Fragment TransactionPayloadFragment{7780a2f} (e7c03c43-e4c2-4ec7-b807-e45ff565b6a1) not attached to a context.
    at androidx.fragment.app.Fragment.requireContext(Fragment.java:977)
    at androidx.fragment.app.Fragment.getResources(Fragment.java:1041)
    at androidx.fragment.app.Fragment.getString(Fragment.java:1063)
    at com.chuckerteam.chucker.internal.ui.transaction.TransactionPayloadFragment$processPayload$2.invokeSuspend(TransactionPayloadFragment.kt:458)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:829)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
      Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@b4c5e9f, Dispatchers.Main.immediate]

cc @jkmassel

@adalpari
Copy link
Contributor

adalpari commented Dec 2, 2025

Also, I noted what appears to be a crash in Chucker when quickly switching tabs, but this may be unrelated to our code. I share it nonetheless.

I was able to reproduce this crash. The bug is in Chucker UI implementation and unfortunately I don't think there is anything we can do on our end.

E  FATAL EXCEPTION: main
  Process: com.jetpack.android.beta, PID: 5719
  java.lang.IllegalStateException: Fragment TransactionPayloadFragment{7780a2f} (e7c03c43-e4c2-4ec7-b807-e45ff565b6a1) not attached to a context.
    at androidx.fragment.app.Fragment.requireContext(Fragment.java:977)
    at androidx.fragment.app.Fragment.getResources(Fragment.java:1041)
    at androidx.fragment.app.Fragment.getString(Fragment.java:1063)
    at com.chuckerteam.chucker.internal.ui.transaction.TransactionPayloadFragment$processPayload$2.invokeSuspend(TransactionPayloadFragment.kt:458)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:829)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
      Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@b4c5e9f, Dispatchers.Main.immediate]

Do we need to give the user access to this screen? Can we log it internally and access it any other way?

@oguzkocer
Copy link
Contributor Author

Do we need to give the user access to this screen? Can we log it internally and access it any other way?

If we are using Chucker, we have to use its UI and there is no way to access the data as far as I can tell. We discussed the possibility of building something in-house with @jkmassel and the proposed TrackNetworkRequestsInterceptor design from this PR should make that switch relatively easy.

I have not been a big fan of using Chucker, but it does make sense to use that initially to test the flow and replace it with our implementation later. I think that's one reason Jeremy wanted to put this behind a feature flag as we currently don't expect end users to use this feature.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 2, 2025

@dcalhoun
Copy link
Member

dcalhoun commented Dec 2, 2025

@oguzkocer I note the GutenbergKit network activity now displays in Chucker, but the timestamp and duration appear incorrect. Is the data provided by GutenbergKit incompatible or empty?

Incorrect GutenbergKit timestamps and durations screenshot

Screenshot_20251202_141257_Jetpack Pre-Alpha

@oguzkocer
Copy link
Contributor Author

@oguzkocer I note the GutenbergKit network activity now displays in Chucker, but the timestamp and duration appear incorrect. Is the data provided by GutenbergKit incompatible or empty?

@dcalhoun This is a known limitation of the replay approach. The ReplayInterceptor returns the pre-recorded response immediately without making a real network call, so:

  • Timestamp: Chucker records when the replay happens, not when the original JS fetch executed
  • Duration: Shows ~0ms since the replay returns instantly (response is already available)

Chucker doesn't have an API to inject custom timestamps - it always uses its own timing. We could simulate duration by adding a wait time to the replay, but that adds overhead without much benefit. If we switch to an in-house tracking implementation in the future, we'd be able to record these values correctly.

@dcalhoun
Copy link
Member

dcalhoun commented Dec 2, 2025

@oguzkocer ah, thank you for explaining. The current approach makes sense to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants